package ejemplo.util;
/**
 * Clase que permite calcular la penetracion de una FormaColisionable sobre otra cuando se produce
 * una colision entre ambas.
 * @author Miguel Vicente
 */
public class UtilidadColision {
	/**
	 * Devuelve el Vector2D de penetracion de f1 sobre f2, siendo f1 y f2 objetos FormaColisionable
	 * de tipo RECTANCULAR o CIRCULAR.
	 * Si f1 y f2 no estan colisionando, devuelve el vector (0,0)
	 * @param f1 FormaColisionable que colisiona con f2
	 * @param f2 FormaColisionable con la que colisiona f1
	 * @return Vector2D
	 */
	public static Vector2D getPenetracion(IForma f1, IForma f2) {
		if (f1.getTipoForma() == IForma.RECTANGULAR && f2.getTipoForma() == IForma.RECTANGULAR) {
			return getPenetracionAABBsobreAABB((AABB) f1, (AABB) f2);
		}
		if (f1.getTipoForma() == IForma.CIRCULAR && f2.getTipoForma() == IForma.RECTANGULAR) {
			return getPenetracionCirculoSobreAABB((FormaCircular) f1, (AABB) f2);
		}
		if (f1.getTipoForma() == IForma.RECTANGULAR && f2.getTipoForma() == IForma.CIRCULAR) {
			Vector2D penetracion = getPenetracionCirculoSobreAABB((FormaCircular) f1, (AABB) f2);
			penetracion.cambiarSentido();
			return penetracion;
		}
		if (f1.getTipoForma() == IForma.CIRCULAR && f2.getTipoForma() == IForma.CIRCULAR) {
			return getPenetracionCirculoSobreCirculo((FormaCircular) f1, (FormaCircular) f2);
		}
		return new Vector2D();
	}
	/**
	 * Devuelve un Vector2D que contiene la penetracion, de un objeto AABB sobre otro objeto AABB.
	 * El sentido de las componentes de dicho vector es el sentido en el que se debe mover el objeto 1
	 * para corregir la colision (por lo general solo hace falta la menor de las componentes (Separated
	 * Axis Theorem), pero se devuelven ambas para una mayor versatilidad).
	 * @param aabb1 AABB
	 * @return Vector2D con la penetracion en X e Y.
	 */
	private static Vector2D getPenetracionAABBsobreAABB(AABB aabb1, AABB aabb2) {
		int signoX, signoY;
		int superposicionX, superposicionY;
		
		// Calculamos la superposicion en cada eje
		superposicionX = (int) ((aabb1.getMitadAncho() + aabb2.getMitadAncho()) - (Math.abs(aabb1.getCentro().getX() - Math.abs(aabb2.getCentro().getX()))));
		superposicionY = (int) ((aabb1.getMitadAlto() + aabb2.getMitadAlto()) - (Math.abs(aabb1.getCentro().getY() - Math.abs(aabb2.getCentro().getY()))));
		// Comprobamos si hay colision
		if ((superposicionX > 0) && (superposicionY > 0)) {
			// Calculamos el sentido del vector en X e Y
			if (aabb1.getCentro().getX() < aabb2.getCentro().getX()) {
				signoX = -1;
			} else {
				signoX = 1;
			}
			if (aabb1.getCentro().getY() < aabb2.getCentro().getY()) {
				signoY = -1;
			} else {
				signoY = 1;
			}
			return new Vector2D(signoX * superposicionX, signoY * superposicionY);
		}
		return new Vector2D(0, 0);
	}
	/**
	 * Calculamos el Vector2D de penetracion de una FormaCircular sobre un AABB.
	 * @param circulo FormaCircular
	 * @param aabb AABB
	 * @return Vector2D
	 */
	private static Vector2D getPenetracionCirculoSobreAABB(FormaCircular circulo, AABB aabb) {
		int xVector, yVector;
		Vector2D vector = null;
		int region = getRegionVoronoiCirculoSobreAABB(circulo, aabb);
		
		switch (region) {
		// Si el centro esta dentro de una de las regiones 0, 2, 6, 8 calculamos el vector que va
		// desde el vertice mas cercano del aabb al centro del circulo y a partir de el obtenemos
		// la penetracion
		case 0:
			// Esquina superior izquierda
			xVector = circulo.getCentroX() - aabb.getBounds().x;
			yVector = circulo.getCentroY() - aabb.getBounds().y;
			vector = calcularPenetracionCirculosobreAABB(xVector, yVector, circulo);
			break;
		case 2:
			// Esquina superior derecha
			xVector = circulo.getCentroX() - aabb.getBounds().x + aabb.getBounds().width;
			yVector = circulo.getCentroY() - aabb.getBounds().y;
			vector = calcularPenetracionCirculosobreAABB(xVector, yVector, circulo);
			break;
		case 6:
			// Esquina inferior izquierda
			xVector = circulo.getCentroX() - aabb.getBounds().x;
			yVector = circulo.getCentroY() - aabb.getBounds().y + aabb.getBounds().height;
			vector = calcularPenetracionCirculosobreAABB(xVector, yVector, circulo);
			break;
		case 8:
			// Esquina inferior derecha
			xVector = circulo.getCentroX() - aabb.getBounds().x + aabb.getBounds().width;
			yVector = circulo.getCentroY() - aabb.getBounds().y + aabb.getBounds().height;
			vector = calcularPenetracionCirculosobreAABB(xVector, yVector, circulo);
			break;
		// Si el centro esta en una de las siguientes zonas, se trata la colision como si fuesen AABBs
		case 1:
		case 3:
		case 4:
		case 5:
		case 7:
			int x = circulo.getCentroX() - circulo.getRadio();
			int y = circulo.getCentroY() - circulo.getRadio();
			int diametro = circulo.getRadio() * 2;
			AABB cajaCirculo = new AABB(x, y, diametro, diametro);
			vector = getPenetracionAABBsobreAABB(cajaCirculo, aabb);
			break;

		default:
			break;
		}
		if (vector == null) { // Si no entra en ningun case, devuelve el vector (0,0)
			vector = new Vector2D();
		}
	    return vector;
	}
	/**
	 * Calculamos la region de Voronoi en la que se encuentra el centro de una FormaCircular con
	 * respecto a un AABB.
	 * @param circulo FormaCircular
	 * @param aabb AABB
	 * @return 0-8
	 */
	private static int getRegionVoronoiCirculoSobreAABB(FormaCircular circulo, AABB aabb) {
		/*
		 * Regiones de Voronoi:
		 * 0 1 2
		 * 3 4 5
		 * 6 7 8
		 * El numero 4 representa el cuadrado.
		 */
		int zonaX, zonaY, zona;
		// Calculamos la region en la que se encuentra el centro del circulo
		if (circulo.getCentroX() < (aabb.getCentro().getX() - aabb.getMitadAncho())) {
			zonaX = 0;
		} else if (circulo.getCentroX() > (aabb.getCentro().getX() + aabb.getMitadAncho())) {
			zonaX = 2;
		} else {
			zonaX = 1;
		}
		if (circulo.getCentroY() < (aabb.getCentro().getY() - aabb.getMitadAlto())) {
			zonaY = 0;
		} else if (circulo.getCentroY() > (aabb.getCentro().getY() + aabb.getMitadAlto())) {
			zonaY = 2;
		} else {
			zonaY = 1;
		}
		zona = zonaX + 3*zonaY;
		return zona;
	}
	/**
	 * Calcula el vector de penetracion del circulo sobre el AABB dadas las componentes del vector
	 * que une el centro del circulo con un vertice del AABB. En caso de que no haya colision
	 * devuelve el vector (0,0)
	 * @param x Componente X del vertice
	 * @param y Componente Y del vertice
	 * @param circulo FormaCircular
	 * @return Vector2D
	 */
	private static Vector2D calcularPenetracionCirculosobreAABB(int x, int y, FormaCircular circulo) {
		double moduloVector, moduloPenetracion;
		Vector2D vectorPenetracion;
		Vector2D vector = new Vector2D(x, y);
		moduloVector = vector.getModulo();
		if (moduloVector < circulo.getRadio()) { // Colision
			moduloPenetracion = circulo.getRadio() - moduloVector;
			vectorPenetracion = vector.calcularVectorProporcional(moduloPenetracion);
		} else {
			vectorPenetracion = new Vector2D();
		}
		return vectorPenetracion;
	}
	/**
	 * Calculamos el Vector2D de penetracion de una FormaCircular sobre otra.
	 * @param f1 FormaCircular que colisiona
	 * @param f2 FormaCircular con la que colisiona f1
	 * @return Vector2D
	 */
	private static Vector2D getPenetracionCirculoSobreCirculo(FormaCircular f1, FormaCircular f2) {
		double moduloVector, moduloPenetracion;
		Vector2D vectorPenetracion;
		int xVector = f2.getCentroX() - f1.getCentroX();
		int yVector = f2.getCentroY() - f2.getCentroY();
		Vector2D vecUnionCentros = new Vector2D(xVector, yVector); // vector que va desde f1 a f2
		moduloVector = vecUnionCentros.getModulo();
		if (moduloVector < (f1.getRadio() + f2.getRadio())) { // Colision
			moduloPenetracion = (f1.getRadio() + f2.getRadio()) - moduloVector;
			vectorPenetracion = vecUnionCentros.calcularVectorProporcional(moduloPenetracion);
		} else {
			vectorPenetracion = new Vector2D();
		}
		return vectorPenetracion;
	}

}
